#if os(iOS)
import XCTest
import ImageIO
@testable import Gifu

private let imageData = testImageDataNamed("mugen.gif")
private let staticImage = UIImage(data: imageData)!
private let preloadFrameCount = 20

class DummyAnimatable: GIFAnimatable {
  init() {}
  var animator: Animator? = nil
  var image: UIImage? = nil
  var layer = CALayer()
  var frame: CGRect = .zero
  var contentMode: UIView.ContentMode = .scaleToFill
  func animatorHasNewFrame() {}
}

class GifuTests: XCTestCase {
  var animator: Animator!
  var originalFrameCount: Int!
  let delegate = DummyAnimatable()

  override func setUp() {
    super.setUp()
    animator = Animator(withDelegate: delegate)
    animator.prepareForAnimation(withGIFData: imageData, size: staticImage.size, contentMode: .scaleToFill)
    originalFrameCount = 44
  }
  
  func testIsAnimatable() {
    XCTAssertNotNil(animator.frameStore)
    guard let store = animator.frameStore else { return }
    XCTAssertTrue(store.isAnimatable)
  }

  func testCurrentFrame() {
    XCTAssertNotNil(animator.frameStore)
    guard let store = animator.frameStore else { return }
    XCTAssertEqual(store.currentFrameIndex, 0)
  }

  func testFramePreload() {
    XCTAssertNotNil(animator.frameStore)
    guard let store = animator.frameStore else { return }

    let expectation = self.expectation(description: "frameDuration")

    store.prepareFrames {
      let animatedFrameCount = store.animatedFrames.count
      XCTAssertEqual(animatedFrameCount, self.originalFrameCount)
      XCTAssertNotNil(store.frame(at: preloadFrameCount - 1))
      XCTAssertNil(store.frame(at: preloadFrameCount + 1)?.images)
      XCTAssertEqual(store.currentFrameIndex, 0)

      store.shouldChangeFrame(with: 1.0) { hasNewFrame in
        XCTAssertTrue(hasNewFrame)
        XCTAssertEqual(store.currentFrameIndex, 1)
        expectation.fulfill()
      }
    }

    waitForExpectations(timeout: 1.0) { error in
      if let error = error {
        print("Error: \(error.localizedDescription)")
      }
    }
  }

  func testFrameInfo() {
    XCTAssertNotNil(animator.frameStore)
    guard let store = animator.frameStore else { return }

    let expectation = self.expectation(description: "testFrameInfoIsAccurate")

    store.prepareFrames {
      let frameDuration = store.frame(at: 5)?.duration ?? 0
      XCTAssertTrue((frameDuration - 0.05) < 0.00001)

      let imageSize = store.frame(at: 5)?.size ?? CGSize.zero
      XCTAssertEqual(imageSize, staticImage.size)

      expectation.fulfill()
    }

    waitForExpectations(timeout: 1.0) { error in
      if let error = error {
        print("Error: \(error.localizedDescription)")
      }
    }
  }

  func testFinishedStates() {
    animator = Animator(withDelegate: delegate)
    animator.prepareForAnimation(withGIFData: imageData, size: staticImage.size, contentMode: .scaleToFill, loopCount: 2)

    XCTAssertNotNil(animator.frameStore)
    guard let store = animator.frameStore else { return }

    let expectation = self.expectation(description: "testFinishedStatesAreSetCorrectly")

    store.prepareFrames {
      let animatedFrameCount = store.animatedFrames.count
      XCTAssertEqual(store.currentFrameIndex, 0)

      // Animate through all the frames (first loop)
      for frame in 1..<animatedFrameCount {
        XCTAssertFalse(store.isLoopFinished)
        XCTAssertFalse(store.isFinished)
        store.shouldChangeFrame(with: 1.0) { hasNewFrame in
          XCTAssertTrue(hasNewFrame)
          XCTAssertEqual(store.currentFrameIndex, frame)
        }
      }

      XCTAssertTrue(store.isLoopFinished, "First loop should be finished")
      XCTAssertFalse(store.isFinished, "Animation should not be finished yet")

      store.shouldChangeFrame(with: 1.0) { hasNewFrame in
        XCTAssertTrue(hasNewFrame)
      }

      XCTAssertEqual(store.currentFrameIndex, 0)

      // Animate through all the frames (second loop)
      for frame in 1..<animatedFrameCount {
        XCTAssertFalse(store.isLoopFinished)
        XCTAssertFalse(store.isFinished)
        store.shouldChangeFrame(with: 1.0) { hasNewFrame in
          XCTAssertTrue(hasNewFrame)
          XCTAssertEqual(store.currentFrameIndex, frame)
        }
      }

      XCTAssertTrue(store.isLoopFinished, "Second loop should be finished")
      XCTAssertTrue(store.isFinished, "Animation should be finished (loopCount: 2)")

      expectation.fulfill()
    }

    waitForExpectations(timeout: 1.0) { error in
      if let error = error {
        print("Error: \(error.localizedDescription)")
      }
    }
  }
}

private func testImageDataNamed(_ name: String) -> Data {
  let testBundle = Bundle(for: GifuTests.self)
  let imagePath = testBundle.bundleURL.appendingPathComponent(name)
  return (try! Data(contentsOf: imagePath))
}
#endif
